import arrow

from django.conf import settings

from utils3.url import get_host, ensure_ip

from utilities.enums import SEVERITY_MAP, CONFIDENCE_MAP, TaskType, EXTaskType, DEFAULT_CLOUD_ABNORMAL_NODES, AvailabilityNormalStatus
from utilities.target import check_availability_is_normal

from models.task import ScanEvents

MAX_RESPONSE_TIME = settings.MAX_RESPONSE_TIME


class MetaClass(object):
    task_type = ''

    def __init__(self):
        self.event_list = []

    def append_event(self, event):
        self.event_list.append(event)

    def set_events(self, event_list: list):
        self.event_list.extend(event_list)

    @staticmethod
    def get_severity(event):
        return event.get('severity', 0)


class AvailabilityParser(MetaClass):
    task_type = TaskType.availability.value

    def parse_site_info(self, event):
        detail = event.get('detail', {})
        cloud = detail.get('cloud')
        if cloud:
            for k in ('http', 'ping'):
                for item in cloud.get(f'detail_{k}', []):
                    item['has_error'] = getattr(self, f"_check_{k}_resp_time")(
                        item.get('resp_time')
                    )

            detail['cloud'] = cloud
        return detail, event.get('event_name')

    def summary(self):
        allow_key = ('dns_hijack', 'http_get_full_time')
        res = {}
        for event in self.event_list:
            detail = event.get('detail', {})
            for k in allow_key:
                res[k] = detail.get(k)

        return {'data': res, 'score': 0}

    def get_normal(self, site_info_conf, target, is_cloud=False):
        modules = site_info_conf.get('modules', [])
        cloud_abnormal_node = target.job_config_map.availability.cloud_abnormal_node or DEFAULT_CLOUD_ABNORMAL_NODES
        normal_status = AvailabilityNormalStatus.default.value
        cloud_normal_status = AvailabilityNormalStatus.default.value
        local_normal_status = AvailabilityNormalStatus.default.value
        target_url = target.target_url
        is_private_ip = False
        iso_created_at = None
        is_check = False
        res_dict = {'local': {}, 'cloud': {}}
        if 'http_get' in modules:
            is_check = True
        if is_check:
            for event in self.event_list:
                iso_created_at = arrow.get(event.get('created_at')).isoformat()
                detail = event.get('detail', {})
                for k, v in detail.items():
                    if k not in modules:
                        continue
                    if k == 'cloud':
                        cloud_normal_status = self._check_cloud(detail, cloud_abnormal_node)
                    elif k == 'http_get_full_time':
                        local_normal_status = self.check_http_get_full_time(detail)
        res_dict['cloud'] = {
            'normal_status': cloud_normal_status,
            'created_at': iso_created_at,
        }
        res_dict['local'] = {
            'normal_status': local_normal_status,
            'created_at': iso_created_at
        }
        host = get_host(target_url)
        if ensure_ip(host) and ensure_ip(host).is_private:
            is_private_ip = True
        if is_private_ip or is_cloud is False or local_normal_status == cloud_normal_status:
            normal_status = local_normal_status
        else:
            indices = [value for value in [local_normal_status, cloud_normal_status] if value != AvailabilityNormalStatus.default.value]
            if len(indices) == 2:
                normal_status = AvailabilityNormalStatus.normal.value
            else:
                normal_status = indices[0]
        res_dict['normal_status'] = normal_status
        res_dict['is_check'] = is_check

        return res_dict

    def check_http_get_full_time(self, detail):
        http_get_full_time = detail.get('http_get_full_time', {})
        status = http_get_full_time.get('status') or 0
        normal_status = (
            AvailabilityNormalStatus.normal.value
            if check_availability_is_normal(status)
            else AvailabilityNormalStatus.abnormal.value
        )
        return normal_status

    @staticmethod
    def _check_cloud(detail, cloud_abnormal_node):
        normal_status = AvailabilityNormalStatus.default.value
        abnormal_node = 0
        normal_node = 0

        cloud = detail.get('cloud', {})
        for item in cloud.get('detail_http', []):
            if item.get('has_error', False) is True:
                abnormal_node += 1
            else:
                status_code = item.get('status_code', 0)
                if not check_availability_is_normal(status_code):
                    abnormal_node += 1
                else:
                    normal_node += 1

        normal_status = (
            AvailabilityNormalStatus.normal.value
            if abnormal_node < cloud_abnormal_node and normal_node
            else AvailabilityNormalStatus.abnormal.value
        )

        return normal_status


class TargetInfoParser(MetaClass):
    task_type = EXTaskType.target_info.value


class SecurityEventParser(MetaClass):
    task_type = TaskType.securityEvent.value

    @staticmethod
    def parse_statistics(event):
        detail = event.get('detail')
        broken_links = detail.get('broken_links', [])
        if broken_links:
            return {
                'results': broken_links,
                'url': event.get('target_url', ''),
            }, 'broken_links'
        else:
            return None, None

    @staticmethod
    def get_count(event):
        event_name = event.get('event_name')

        return event_name, 1

    @staticmethod
    def get_severity(event):
        # Skip without broken_links.
        event_name = event.get('event_name')
        broken_link = event.get('detail', {}).get('broken_links')
        if event_name == 'statistics':
            if broken_link:
                return 2
            else:
                return -1

        return event.get('severity', 0)

    @staticmethod
    def get_risk_detail_name(event):
        if event.get('event_type') == 4:
            if not event.get('detail', {}).get('results'):
                return None
            return 'broken_links'
        return event.get('event_name')


class VulParser(MetaClass):
    task_type = TaskType.vul.value
    severity_map = SEVERITY_MAP

    @staticmethod
    def get_severity(event):
        if event.get('event_name') == 'statistics':
            return -1

        return event.get('detail', {}).get('severity', 0)

    @staticmethod
    def get_count(event):
        return event.get('severity_name', 0), 1

    @staticmethod
    def get_risk_detail_name(event):
        if event.get('event_type') == 4:
            return None
        return event.get('detail').get('vuln_type', [{}])[0].get('zh_cn', '')


class RiskLinkParser(MetaClass):
    task_type = TaskType.risk_link.value

    @staticmethod
    def get_severity(event):
        if event.get('event_name') == 'statistics':
            return -1

        confidence = event.get('detail', {}).get('confidence', 0)
        return CONFIDENCE_MAP.get(confidence)

    @staticmethod
    def get_count(event):
        return event.get('severity_name', 0), 1

    @staticmethod
    def get_risk_detail_name(event):
        if event.get('event_type') == 4:
            return None
        return 'risk_link'


class ChangeCheckParser(MetaClass):
    task_type = TaskType.change_check.value
    severity_map = SEVERITY_MAP

    @staticmethod
    def get_severity(event):
        if event.get('event_name') == 'statistics':
            return -1

        return event.get('severity', 0)

    @staticmethod
    def get_count(event):
        return event.get('severity_name', 0), 1

    @staticmethod
    def get_risk_detail_name(event):
        if event.get('event_type') == 4:
            # statistics
            return None
        return 'change_check'


def group_risk_link(events):
    for event in events:
        results_dict = {}
        for item in event.get('detail', {}).get('results', []):
            results_dict.setdefault(item.get('risk_url'), []).append(item)

        event['detail']['results'] = results_dict

    return events
